/*
 * 作者：李江军
 * 日期：2022-07-28
 *
 * 该测试程序验证内容：
 * 1. 使用 socket 发送 / 接收带有 string 类型的结构体
 * 2. 如何在服务器端更好的架构监听、接收客户端、接收消息等。
 */

#include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/shm.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <thread>
#include <vector>
#include <stdlib.h>

using namespace std;

struct NetMsg {
    char    name[32];
    short   age;
    char    sex[16];
    float   height;
    float   weight;
};

class Server
{
public:
    Server(string ip, short port) : m_addr(ip), m_port(port) {}
    ~Server() {
        for(auto conn: sock_arr)
            close(conn);
        close(m_sockfd);
    }

    void run();
    static void RecvMsg(int conn);
private:
    string      m_addr;
    short       m_port;

    int         m_sockfd;   // 服务器监听套接字
    vector<int> sock_arr;   //保存所有套接字描述符
};

void Server::run()
{
    m_sockfd = socket(AF_INET, SOCK_STREAM, 0);

    // 定义sockaddr_in 
    struct sockaddr_in server_sockaddr;
    server_sockaddr.sin_family = AF_INET;                           // TCP/IP协议族
    server_sockaddr.sin_port = htons(m_port);                       // server_port;//端口号
    server_sockaddr.sin_addr.s_addr = inet_addr(m_addr.c_str());    // ip地址，127.0.0.1 是环回地址，相当于本机ip

    // bind，成功返回 0，出错返回 -1                                                                                              
    if(bind(m_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)                                    
    {                                                                                                                          
        perror("bind failed");      // 输出错误原因                                                                                          
        exit(1);                    // 结束程序                                                                                                     
    }
    cout << "服务绑定成功！" << endl;
                                                                                                                               
    // listen，成功返回0，出错返回-1                                                                                            
    if(listen(m_sockfd, 20) == -1)                                                                                         
    {                                                                                                                          
        perror("listen failed.");   // 输出错误原因                                                                                        
        exit(1);                    // 结束程序                                                                                                     
    }
    cout << "服务监听成功！监听 IP 地址：" << m_addr << ", 端口号：" << m_port << endl;
                                                                                                                               
    // 客户端套接字                                                                                                             
    struct sockaddr_in client_addr;                                                                                            
    socklen_t length = sizeof(client_addr);
    // 不断取出新连接并创建子线程为其服务                                                                                       
    while(1){                                                                                                                  
        int conn = accept(m_sockfd, (struct sockaddr*)&client_addr, &length);                                             
        if(conn < 0)                                                                                                             
        {                                                                                                                      
            perror("connect failed.");  //输出错误原因                                                                                   
            exit(1);                    //结束程序                                                                                                 
        }
        cout << "新客户端连接了" << endl;   
        cout << "文件描述符：" << conn << "， IP 地址：" << client_addr.sin_addr.s_addr << "， 端口号：" << client_addr.sin_port << endl;                                                                      
        sock_arr.push_back(conn);

        // 创建线程
        thread t(Server::RecvMsg, conn);                                                                                       
        t.detach();                     //置为分离状态，不能用join，join会导致主线程阻塞                                                            
    } 
}

// 子线程工作的静态函数
// 注意，前面不用加 static，否则会编译报错
void Server::RecvMsg(int conn)
{
    while(1)
    {
        NetMsg msg;
        int len = recv(conn, &msg, sizeof(msg), 0);
        cout << "姓名：" << msg.name << ", 年龄：" << msg.age << ", 性别：" << msg.sex << ", 身高：" << msg.height << ", 体重：" << msg.height << endl;

        // 将接收到的数据发送回去
        // len = send(conn, buffer, strlen(buffer), 0);
    }
}
  

int main(int argc, char* argv[])
{
    string addr = "127.0.0.1";
    short port = 8989;
    Server s(addr, port);
    s.run();
    return 0;
}